﻿using System;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;

namespace WpfStarterKit.Framework.ViewModel
{
    /// <summary>
    /// This is the base class for all view models that sit behind the tablet views. Provides a bunch of convenient overridable
    /// methods on top of BaseViewModel. These methods can be used by the view model to perform actions depending upon the View's
    /// state
    /// </summary>
    public class UserInterfaceViewModel : BaseViewModel
    {
        /// <summary>
        /// Occurs when [initialized].
        /// </summary>
        public event EventHandler Initialized;

        /// <summary>
        /// Raises the Initialized event.
        /// </summary>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        public void MarkInitialized(EventArgs e)
        {
            OnInitialized(e);
        }

        /// <summary>
        /// Raises the Initialized event.
        /// </summary>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected virtual void OnInitialized(EventArgs e)
        {
            EventHandler copy = Initialized;

            // if at this point another thread unsubscribes, 
            // our copy remains unaffected --> no exceptions 
            if (copy != null)
            {
                copy(this, e);
            }
        }

        /// <summary>
        /// This virtual method is called when the view's Loaded event handler is hit
        /// </summary>
        public virtual void Initialize()
        {
        }

        /// <summary>
        /// This virtual method is called when the view model is removed from a modal view stack. This will happen
        /// when another view calls RemoveBackEntry on the NavigationManager and this view model is on top of the stack
        /// </summary>
        /// <param name="removeRequestedBy">Navigation action of the view model that requested removal</param>
        public virtual void RemovedFromStack(string removeRequestedBy)
        {
        }

        /// <summary>
        /// Gets or sets a value indicating whether this instance is initialized.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is initilized; otherwise, <c>false</c>.
        /// </value>
        protected bool IsInitialized { get; set; }

        /// <summary>
        /// Picks up properties (and values) decorated with the ProvideAttribute and
        /// returns them. For properties that are targetted (specifies a TargetNavigationAction) values are only returned if
        /// the target matches the requestor.
        /// </summary>
        /// <param name="requestor">Requesting view model</param>
        /// <returns>ProvidedValues collection</returns>
        public virtual ProvidedValuesDictionary ProvideValues(string requestor)
        {
            ProvidedValuesDictionary providedValues = new ProvidedValuesDictionary();
            Dictionary<ProviderKey, object> localProviderValues = new Dictionary<ProviderKey, object>();

            var classHierarchy = new Dictionary<string, short>();
            var viewModelType = GetType();
            UpdateTypeHierarchy(viewModelType, classHierarchy, 0);

            PropertyInfo[] providers = viewModelType.GetProperties();
            if (providers != null && providers.Count() > 0)
            {
                foreach (PropertyInfo provider in providers)
                {
                    ProvideAttribute pa = GetProvider(provider);
                    if (pa != null)
                    {
                        if (pa.TargetNavigationAction == null || (pa.TargetNavigationAction != null && pa.TargetNavigationAction == requestor))
                        {
                            UpdateProviderValue(pa.PropertyName, provider.DeclaringType.ToString()
                                                , classHierarchy, provider.GetValue(this, null)
                                                , localProviderValues);
                        }
                    }
                }
            }

            foreach (var providerKeyValue in localProviderValues)
            {
                providedValues.Add(providerKeyValue.Key.KeyName, providerKeyValue.Value);
            }

            return providedValues;
        }

        private static void UpdateTypeHierarchy(Type type, Dictionary<string, short> hierarchy, short level)
        {
            hierarchy.Add(type.ToString(), level);
            if (type.BaseType != null)
            {
                UpdateTypeHierarchy(type.BaseType, hierarchy, ++level);
            }
        }

        private static void UpdateProviderValue(
                                string wellKnownPropertyName
                                , string declaringTypeName
                                , Dictionary<string, short> classHierarchy
                                , object providerValue
                                , Dictionary<ProviderKey, object> providerValues)
        {

            short declaringTypeLevel = classHierarchy[declaringTypeName];

            KeyValuePair<ProviderKey, object> keyValue =
                    new KeyValuePair<ProviderKey, object>(
                        new ProviderKey
                        {
                            KeyName = wellKnownPropertyName
                          ,
                            KeyDeclarationLevel = declaringTypeLevel
                        }
                        , providerValue);



            var providerKey = (from key in providerValues
                               where string.Compare(key.Key.KeyName, wellKnownPropertyName, true) == 0
                               select key).SingleOrDefault();

            //current provider value is of the base type, remplace it with the inherited type
            if (providerKey.Key != null && providerKey.Key.KeyDeclarationLevel > declaringTypeLevel)
            {
                providerValues.Remove(providerKey.Key);
                providerValues.Add(keyValue.Key, keyValue.Value);
            }
            //provide value does not exist in the cache
            else if (providerKey.Key == null)
            {
                providerValues.Add(keyValue.Key, keyValue.Value);
            }
            else
            {
                //ignore base value
            }

        }

        /// <summary>
        /// Consumes provided values
        /// </summary>
        /// <param name="sender">Sender</param>
        /// <param name="providedValues">ProvidedValues</param>
        public virtual void ConsumeValues(string sender, ProvidedValuesDictionary providedValues)
        {
            if (providedValues != null)
            {
                PropertyInfo[] consumers = GetType().GetProperties();
                if (consumers != null && consumers.Count() > 0)
                {
                    foreach (PropertyInfo consumer in consumers)
                    {
                        ConsumeAttribute ca = GetConsumer(consumer);
                        if (ca != null)
                        {
                            if (providedValues.ContainsKey(ca.PropertyName) && IsValueApplicable(ca, sender))
                            {
                                object providedValue = providedValues[ca.PropertyName];
                                consumer.SetValue(this, providedValue, null);
                            }
                            else if (ca.ClearIfNoValue && IsNullableType(consumer.PropertyType))
                            {
                                consumer.SetValue(this, null, null);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Call any return methods that need calling
        /// </summary>
        /// <param name="fromNavigationAction">The navigation action that the control is being tranferred from</param>
        public virtual void CallReturnMethods(string fromNavigationAction)
        {
            var methods = GetType().GetMethods();
            if (methods != null && methods.Count() > 0)
            {
                foreach (var method in methods)
                {
                    var rma = ReturnMethodAttribute.GetAttribute(method);
                    if (rma != null)
                    {
                        if (rma.FromNavigationAction == fromNavigationAction)
                        {
                            method.Invoke(this, null);
                        }
                    }
                }
            }
        }

        private static bool IsNullableType(Type t)
        {
            if (t.IsValueType == false)
                return true;

            if (t == typeof(int?))
                return true;

            if (t == typeof(bool?))
                return true;

            if (t == typeof(DateTime?))
                return true;

            return false;
        }

        private static bool IsValueApplicable(ConsumeAttribute ca, string sender)
        {
            if (ca.SourceNavigationAction == null)
                return true;

            if (ca.SourceNavigationAction != null && ca.SourceNavigationAction == sender)
                return true;

            return false;
        }

        static ProvideAttribute GetProvider(MemberInfo mInfo)
        {
            object[] provider = mInfo.GetCustomAttributes(typeof(ProvideAttribute), false);
            if (provider != null && provider.Count() > 0)
            {
                return provider[0] as ProvideAttribute;
            }

            return null;
        }

        static ConsumeAttribute GetConsumer(MemberInfo mInfo)
        {
            object[] consumer = mInfo.GetCustomAttributes(typeof(ConsumeAttribute), false);
            if (consumer != null && consumer.Count() > 0)
            {
                return consumer[0] as ConsumeAttribute;
            }

            return null;
        }
    }

    internal class ProviderKey
    {
        public string KeyName { get; set; }

        public short KeyDeclarationLevel { get; set; }
    }
}
